package com.xbongbong.pro.dataset.parse;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.google.common.collect.Maps;
import com.xbongbong.paas.bi.pojo.dto.DataSetParseDTO;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.constant.XbbConstant;
import com.xbongbong.paas.enums.bi.DataSetNodeTypeEnum;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.pro.bi.commons.helper.CollectionHelper;
import com.xbongbong.pro.bi.commons.helper.StringHelper;
import com.xbongbong.pro.dataset.helper.InvokeTableHelper;
import com.xbongbong.pro.dataset.helper.MapHelper;
import com.xbongbong.pro.dataset.node.NodeStrategy;
import com.xbongbong.pro.dataset.node.NodeStrategyFactory;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.statistic.pojo.dto.SqlParseDTO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.StringConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author gzt
 * @version 1.0
 * @Description -
 * @datetime - 2022/5/17-19:20
 * @since v1.0
 */
@Slf4j
@Component
public class JsonParse {
    @Resource
    private InvokeTableHelper invokeTableHelper;

    /**
     * @author  gzt
     * @Description sql解析入口类
     * @Date 15:26 2022/12/28
     * @param jsonString 前端传入的json参数
     * @param dataSetParseDTO 数据集解析DTO
     * @return java.lang.String
     * @throws
     **/

    public String getSql(JSONObject jsonString, DataSetParseDTO dataSetParseDTO) throws XbbException {
        String lastNodeKey = null;
        AtomicReference<String> sql = new AtomicReference<String>();
        HashMap<String, String> sortedNodes = Maps.newLinkedHashMap();
        Map<String,String> mapNodes = JSON.parseObject(jsonString.toJSONString(),new TypeReference<HashMap<String,String>>(){}, Feature.OrderedField);
        Map<Long,Integer> formIdBussMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        if (dataSetParseDTO.isPreView()) {
            addInputNodeForPreView(sortedNodes, mapNodes, formIdBussMap);
            Set<Map.Entry<String, String>> entries = mapNodes.entrySet();
            try {
                handlerPreView(sortedNodes, entries);
                if (entries.size()==1){
                    dataSetParseDTO.setInputPreView(true);
                }
                lastNodeKey = MapHelper.getTailKeyByReflection(sortedNodes);
            } catch (Exception e) {
                log.error("sql解析-预览-[失败]");
                throw new XbbException(ErrorCodeEnum.API_ERROR_263005);
            }
        }else {
            try {
                handlerNoPreView(sortedNodes, mapNodes, formIdBussMap);
            } catch (Exception e) {
                log.error("sql解析-handlerNoPreView方法异常",e);
            }
        }
        //输入节点没有正确的配置
        if(formIdBussMap.isEmpty()){
            throw new XbbException(ErrorCodeEnum.API_ERROR_263005);
        }
        Map<Long, String> formIdTableMap = invokeTableHelper.getFormIdTableMap(formIdBussMap);

        if (sortedNodes == null) {
            log.error("output 节点必须存在!");
            throw new XbbException(ErrorCodeEnum.API_ERROR_263005);
        }
        Map<String, String> reverseMap = null;
        if (!dataSetParseDTO.isPreView()) {
            reverseMap = CollectionHelper.reverse(sortedNodes);
        }else{
            reverseMap = sortedNodes;
        }
        String finalLastNodeKey = lastNodeKey;
        reverseMap.forEach((nodeId, v)-> {
            JSONObject jsonObject = JSON.parseObject(v);
            String type = (String) jsonObject.get(XbbConstant.TYPE);
            NodeStrategy strategy = NodeStrategyFactory.getStrategy(type);
            try {
                SqlParseDTO sqlParseDTO = new SqlParseDTO(nodeId,jsonObject);
                sqlParseDTO.setFormIdTableMap(formIdTableMap);
                sqlParseDTO.setCorpid(dataSetParseDTO.getCorpid());
                sqlParseDTO.setPreView(dataSetParseDTO.isPreView());
                sqlParseDTO.setPreViewLimit(dataSetParseDTO.getPreViewLimit());
                boolean inputPreView = dataSetParseDTO.isInputPreView();
                if (Objects.nonNull(inputPreView)){
                    sqlParseDTO.setInputPreView(inputPreView);
                }
                sqlParseDTO.setDataSetId(dataSetParseDTO.getDataSetId());
                strategy.parseNodeToSql(sqlParseDTO);
                if ((StringUtils.isNotEmpty(finalLastNodeKey) && StringUtils.equals(nodeId, finalLastNodeKey))
                        || StringUtils.equals(type, DataSetNodeTypeEnum.OUTPUT.getAlias())){
                    String parseSql = strategy.getParseSql(nodeId);
                    //输入节点补充右括号
                    if(StringUtils.equals(type, DataSetNodeTypeEnum.INPUT.getAlias()) && Objects.equals(false,inputPreView)){
                        parseSql =  parseSql+ XbbConstant.SPACE+XbbConstant.R_P+XbbConstant.AS+NodeStrategy.NODE_TABLE_NAME_THREAD_LOCAL.get().get(nodeId);
                    }
                    if (Objects.equals(true,dataSetParseDTO.isPreView())){
                        parseSql = parseSql+XbbConstant.LIMIT+ BasicConstant.TWENTY;
                    }
                    sql.set(parseSql);
                }
            } catch (Exception e) {
                log.error("json解析成sql异常",e);
                //throw new XbbException(ErrorCodeEnum.API_ERROR_263005);
            }
        });
        clearNodeStrategyMap();
        return sql.get();
    }

    /**
     * @author  gzt
     * @Description 清楚map缓存
     * @Date 16:16 2022/9/16
     * @throws
     **/

    private void clearNodeStrategyMap() {
        NodeStrategy.SQL_NODES_THREAD_LOCAL.remove();
        NodeStrategy.NODE_FORM_ID_THREAD_LOCAL.remove();
        NodeStrategy.NODE_TABLE_NAME_THREAD_LOCAL.remove();
        NodeStrategy.NODE_TYPE_THREAD_LOCAL.remove();
    }

    private void handlerNoPreView(HashMap<String, String> sortedNodes, Map<String, String> mapNodes, Map<Long, Integer> formIdBussMap) {
        mapNodes.forEach((nodeId, jsonstr)-> {
            JSONObject jsonObject = JSON.parseObject(jsonstr);
            String type = (String) jsonObject.get(XbbConstant.TYPE);
            if (type != null) {
                if (type.equals(DataSetNodeTypeEnum.OUTPUT.getAlias())){
                    sortedNodes.put(nodeId,jsonstr);
                    JSONArray outputJsonArray = (JSONArray) jsonObject.get(DataSetNodeTypeEnum.INPUT.getAlias());
                    generateOrderedNode(mapNodes, outputJsonArray, sortedNodes);
                }else if(Objects.equals(type, DataSetNodeTypeEnum.INPUT.getAlias())){
                    Long formId = jsonObject.getLong(StringConstant.FORM_ID);
                    Integer businessType = jsonObject.getInteger(StringConstant.BUSINESS_TYPE);
                    formIdBussMap.put(formId,businessType);
                }
            }
        });
    }

    private void addInputNodeForPreView(HashMap<String, String> sortedNodes, Map<String, String> mapNodes, Map<Long, Integer> formIdBussMap) {
        mapNodes.forEach((nodeId, jsonstr)-> {
            JSONObject jsonObject = JSON.parseObject(jsonstr);
            String type = (String) jsonObject.get(XbbConstant.TYPE);
            if (type != null) {
                if(Objects.equals(type, DataSetNodeTypeEnum.INPUT.getAlias())){
                    Long formId = jsonObject.getLong(StringConstant.FORM_ID);
                    Integer businessType = jsonObject.getInteger(StringConstant.BUSINESS_TYPE);
                    formIdBussMap.put(formId,businessType);
                    //预览
                    sortedNodes.put(nodeId,jsonstr);
                }
            }
        });
    }

    private Set<Map.Entry<String, String>> handlerPreView(HashMap<String, String> sortedNodes, Set<Map.Entry<String, String>> entries) {
        Set<Map.Entry<String, String>> nextEntries = entries.stream().filter(x -> {
            if (x == null) {
                return false;
            }
            JSONObject jsonObject = JSON.parseObject(x.getValue());
            String type = (String) jsonObject.get(XbbConstant.TYPE);
            if (type != null) {
                return !Objects.equals(type, DataSetNodeTypeEnum.INPUT.getAlias()) && !Objects.equals(type, DataSetNodeTypeEnum.OUTPUT.getAlias());
            }
            return false;
        }).map(x -> {
            JSONObject jsonObject = JSON.parseObject(x.getValue());
            String type = (String) jsonObject.get(XbbConstant.TYPE);
            if (type != null) {
                JSONArray jsonArray = (JSONArray) jsonObject.get(DataSetNodeTypeEnum.INPUT.getAlias());
                List<Object> containList = jsonArray.stream().filter(v -> !sortedNodes.keySet().contains(v)).collect(Collectors.toList());
                if (containList.size() == 0) {
                    sortedNodes.put(x.getKey(), x.getValue());
                } else {
                    return x;
                }
            }
            return null;
        }).collect(Collectors.toSet());
        if (nextEntries.size()>0 ){
            handlerPreView(sortedNodes, nextEntries);
        }
        return nextEntries;
    }

    private static void generateOrderedNode(Map<String, String> mapNodes, JSONArray jsonArray,HashMap<String, String> sortedNodes) {
        jsonArray.stream().forEach(nodeId -> {
            String node = mapNodes.get(nodeId);
            sortedNodes.put(StringHelper.getString(nodeId),node);
        });
        jsonArray.stream().forEach(nodeId -> {
            String node = mapNodes.get(nodeId);
            JSONObject node1 = JSON.parseObject(node);
            Object inputArray = node1.get(DataSetNodeTypeEnum.INPUT.getAlias());
            if (inputArray == null) {
                return;
            }
            JSONArray subJsonArray = (JSONArray) inputArray;
            generateOrderedNode(mapNodes,subJsonArray,sortedNodes);
        });
    }
}
